package asteroids.model.programs;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import be.kuleuven.cs.som.annotate.Basic;

import asteroids.model.Ship;
import asteroids.model.programs.parsing.statements.*;
import asteroids.model.programs.parsing.types.BooleanLiteral;
import asteroids.model.programs.parsing.types.Type;

public class Program implements Cloneable {

	public Program(Map<String, Type> globals, Statement statement) {
		this.globals = globals;
		this.statement = statement;
		this.nextStatement = statement;

	}

	@Basic
	public Statement getStatement() {
		return statement;
	}

	private Statement statement;

	@Basic
	public Statement getNextStatement() {
		return nextStatement;
	}

	public void setNextStatement(Statement nextStatement) {
		this.nextStatement=nextStatement;
	}

	private Statement nextStatement;

	@Basic
	public Map<String, Type> getGlobals() {
		return globals;
	}

	public void addGlobalVariable(String name, Type type){
		getGlobals().put(name,type);
	}

	private Map<String, Type> globals;

	public Type getVariableValue(String variable){
		return getGlobals().get(variable);
	}

	public void execute(Ship ship) {
		boolean status= false; 		//Determines whether this method needs to be rerun again.
		if(getNextStatement() instanceof Sequence){
			List<Statement> statements =
					((Sequence) getNextStatement()).getAllIndividualStatements(new ArrayList<Statement>());
			List<Statement> statementsToBeExecuted = new ArrayList<Statement>();
			List<Statement> statementsForNextExecution = new ArrayList<Statement>();
			int j=0; // is 1 if an action occured.
			for(Statement s : statements){
				if(j!=1 && status==false){
					if(Action.class.isAssignableFrom(s.getClass())){
						j++;
					}
					//Add the body + while statement again to the list for the next execution.
					if(s.getClass()==WhileStatement.class && ((BooleanLiteral) ((WhileStatement) s).getExpression().eval(ship, null)).getValue()==true){
						statementsForNextExecution.add(((WhileStatement) s).getStatement());
						statementsForNextExecution.add(s);
						status=true;						
					}
					//Add the resulting statement from the if-statement to the list for the next execution.
					if(s instanceof IfStatement){
						statementsForNextExecution.add(((IfStatement) s).getResultStatement(ship));
						status=true;			
					}
					// Add the statement to statementsToBeExecuted
					if(status ==false){
						statementsToBeExecuted.add(s);	
					}
				}
				else{
					statementsForNextExecution.add(s);
				}
			}
			// Execute all the statements that need to be executed.
			new Sequence(statementsToBeExecuted,0,0).execute(ship);
			// If we are at the end of a program, add the entire getStatement() as next statement. 
			if(statementsForNextExecution.isEmpty() || statements.isEmpty()){
				setNextStatement(getStatement());
			}
			//else add statementsForNextExecution
			else{
				setNextStatement(new Sequence(statementsForNextExecution,statementsForNextExecution.get(0).getLine(),statementsForNextExecution.get(0).getColumn()));
			}
			// If we have reached a while or if-statement, the program executes again.
			if(status==true){
				execute(ship);
			}
		}
		else{
			getNextStatement().execute(ship);
		}		
	}

	/**
	 * Returns a list of typecheck errors this program has.
	 */
	public List<String> getTypeCheckErrors() {		
		List<String>result = getStatement().getTypeCheckErrors(new ArrayList<String>(), getGlobals());
		return result;
	}

	@Override
	public Program clone(){
		Map<String, Type> newGlobals = new HashMap<String,Type>();
		for(String key : getGlobals().keySet()){
			newGlobals.put(key, getGlobals().get(key).clone());
		}
		return new Program(newGlobals,getStatement());
	}
}
